Cross Compiling for Windows on Linux
  • Last updated on 31st May 2023

Install Vagga

Vagga is a userspace container built using rust. To install, run:

   cd ~/
   rm -rf vagga*
   wget https://files.zerogw.com/vagga/vagga-0.8.1-86-g505b3ea.tar.xz
   tar -xJf vagga-0.8.1-86-g505b3ea.tar.xz
   cd vagga
   chmod +x install.sh
   sudo ./install.sh
   echo "ubuntu-mirror: http://archive.ubuntu.com/ubuntu
   external-volumes:
     X11: /tmp/.X11-unix/" >~/.vagga.yaml

Setup tauri application

Quickly setup tauri application using the create-tauri-app crate.

cargo install create-tauri-app

Add vagga.yaml file to the root of the application base directory. Additional script commands may be added to an external shell file to keep yaml file neat and easily readable. For this also create a vagga.sh file at the base url and make it executable.

    cd tauri-app
    touch vagga.sh
    chmod +x vagga.sh

Other files that shall be required during compilation are app.wxs and app.manifest files. The app.wxs file defines the wix configuration settings to be used while creating a .msi executable. app.manifest is included to describe and identify assemblies that prevent the compiled executable file from running into errors.

You will also need to create a License.rtf file.

Resulting directory structure should look like

├── tauri-app
    ├── src
    ├── src-tauri
    │   ├── src
    │   ├── app.manifest
    │   ├── build.rs
    │   ├── cargo.toml
    │   ├── tauri.conf.json
    ├── .gitignore
    ├── app.wxs
    ├── License.rtf
    ├── README.md
    ├── vagga.sh
    ├── vagga.yaml

File Modifications

cargo.toml

Add:

[build-dependencies]
embed-manifest = "1.3.1"

build.rs

use embed_manifest::{embed_manifest, new_manifest};

fn main() {
    if std::env::var_os("CARGO_CFG_WINDOWS").is_some() {
        embed_manifest(new_manifest("app.manifest")).expect("unable to embed manifest file");
    }
    tauri_build::build()
}

app.manifest

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
    <assemblyIdentity
            version="1.0.0.0"
            processorArchitecture="*"
            name="Tauri.AppName"
            type="win32"
    />
    <description>Rust Manifest Example</description>
    <dependency>
        <dependentAssembly>
            <assemblyIdentity
                    type="win32"
                    name="Microsoft.Windows.Common-Controls"
                    version="6.0.0.0"
                    processorArchitecture="*"
                    publicKeyToken="6595b64144ccf1df"
                    language="*"
            />
        </dependentAssembly>
    </dependency>
    <asmv3:application>
        <asmv3:windowsSettings>
            <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
            <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
        </asmv3:windowsSettings>
    </asmv3:application>
</assembly>

app.wxs

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Product Id="*" Language="1033" Manufacturer="Tauri Application" Name="tauriApp" Version="0.0.1" UpgradeCode="*">
    <Package InstallScope="perMachine" Compressed="yes" InstallerVersion="450"/>
    <MediaTemplate EmbedCab="yes"/>
    <Icon Id="icon.ico" SourceFile="icon.ico"/>
    <Property Id="ARPPRODUCTICON" Value="icon.ico"/>
    <Property Id="ARPNOREPAIR" Value="yes" Secure="yes"/>
    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFiles64Folder">
        <Directory Id="INSTALLFOLDER" Name="tauriApp">
          <Component Id="MainExecutable" Guid="*" Win64="yes">
            <File Id="tauriAppEXE" KeyPath="yes" Source="tauriApp.exe">
              <Shortcut Id="tauriAppShortcut" Directory="ApplicationProgramsFolder" Name="tauriApp" WorkingDirectory="INSTALLFOLDER" Advertise="yes" Icon="icon.ico" IconIndex="0">
              </Shortcut>
            </File>
            <RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
          </Component>
        </Directory>
      </Directory>
      <Directory Id="ProgramMenuFolder">
        <Directory Id="ApplicationProgramsFolder" Name="tauriApp"/>
      </Directory>
    </Directory>
    <Feature Id="Complete" Level="1">
      <ComponentRef Id="MainExecutable"/>
    </Feature>
    <UIRef Id="WixUI_Minimal"/>
  </Product>
</Wix>

vagga.yaml

most of the cross compilation variables were sourced from: xwin blog post

containers:
  tauri:
    auto-clean: true
    setup:
      - !UbuntuRelease { codename: lunar, eatmydata: false }
      - !Env HOME: /work/.vagga/home
      - !Sh |
          mkdir -p .vagga/home
          mkdir -p .vagga/run/user/root
      - !Sh |
          # install required packages
          apt install -y software-properties-common gpg gpg-agent gnupg-agent
          add-apt-repository -y ppa:apt-fast/stable
          apt-add-repository -y universe
          apt-add-repository -y multiverse
          add-apt-repository -y ppa:vala-team/next
          apt update

          # install base packages and dependencies
          DEBIAN_FRONTEND=noninteractive apt install -y apt-fast axel aria2
          apt-fast install -y curl ca-certificates build-essential libssl-dev pkg-config cmake make tar meson ninja-build git valac bison libgsf-1-dev libglib2.0-dev libgio-qt-dev libgcab-dev libgirepository1.0-dev
          curl --fail https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor > $KEYRINGS/llvm.gpg; 
          echo "deb [signed-by=$KEYRINGS/llvm.gpg] http://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 main" > /etc/apt/sources.list.d/llvm.list;
          apt update

          # install llvm
          apt-fast install -y llvm-15 lld-15 clang-15 libobjc-11-dev
          # ensure that clang/clang++ are callable directly
          ln -s clang-15 /usr/bin/clang && ln -s clang /usr/bin/clang++ && ln -s lld-15 /usr/bin/ld.lld
          # We also need to setup symlinks ourselves for the MSVC shims because they aren't in the debian packages
          ln -s clang-15 /usr/bin/clang-cl && ln -s llvm-ar-15 /usr/bin/llvm-lib && ln -s lld-link-15 /usr/bin/lld-link
          # Use clang instead of gcc when compiling binaries targeting the host (eg proc macros, build files)
          update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100
          update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100

          # msitools build
          cd ~/
          rm -rf msitools
          git clone --recurse-submodules -j8 https://gitlab.gnome.org/GNOME/msitools.git
          cd msitools
          meson build
          ninja -C build
          meson install -C build

          # delete resolv.conf file; causes build time dns errors
          rm -rf /etc/resolv.conf
    environ: &envs
      HOME: /work/.vagga/home
      USER: root
      XDG_RUNTIME_DIR: /work/.vagga/run/user/root
      XDG_CACHE_HOME: /work/.vagga/home/.cache
      PATH: /bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/work/.vagga/home/.local/bin:/work/.vagga/home/.cargo/bin
      CARGO_HOME: /work/.vagga/home/.cargo
      LD_LIBRARY_PATH: /usr/local/lib
      CC_x86_64_pc_windows_msvc: "clang-cl"
      CXX_x86_64_pc_windows_msvc: "clang-cl"
      AR_x86_64_pc_windows_msvc: "llvm-lib"
      CL_FLAGS: "-Wno-unused-command-line-argument -fuse-ld=lld-link /imsvc/xwin/crt/include /imsvc/xwin/sdk/include/ucrt /imsvc/xwin/sdk/include/um /imsvc/xwin/sdk/include/shared"
      CFLAGS_x86_64_pc_windows_msvc: "-Wno-unused-command-line-argument -fuse-ld=lld-link /imsvc/xwin/crt/include /imsvc/xwin/sdk/include/ucrt /imsvc/xwin/sdk/include/um /imsvc/xwin/sdk/include/shared"
      CXXFLAGS_x86_64_pc_windows_msvc: "-Wno-unused-command-line-argument -fuse-ld=lld-link /imsvc/xwin/crt/include /imsvc/xwin/sdk/include/ucrt /imsvc/xwin/sdk/include/um /imsvc/xwin/sdk/include/shared"
      CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER: "lld-link"
      RUSTFLAGS: "-Lnative=/work/.vagga/home/xwin/crt/lib/x86_64 -Lnative=/work/.vagga/home/xwin/sdk/lib/um/x86_64 -Lnative=/work/.vagga/home/xwin/sdk/lib/ucrt/x86_64"
    volumes: &vols
      /tmp: !Tmpfs
        size: 1G
  tools:
    auto-clean: true
    setup:
      - !Container tauri
      - !Env PATH: /bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/work/.vagga/home/.local/bin:/work/.vagga/home/.cargo/bin
      - !Env CARGO_HOME: /work/.vagga/home/.cargo
      - !Env LD_LIBRARY_PATH: /usr/local/lib
      - !Sh |
          ldconfig

          # install rust
          curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

          # install cargo build packages
          cargo install xwin --git https://github.com/Jake-Shadle/xwin.git --rev 11e18b581aad2158b84f2c689d6c294edba5ec29
          rm -rf /work/.vagga/home/xwin
          xwin --accept-license splat --output /work/.vagga/home/xwin
          rm -rf .xwin-cache /usr/local/cargo/bin/xwin

    environ: *envs
    volumes: *vols
commands:
  init: !CapsuleCommand
    run: vagga _capsule build tauri
    description: initialize container
  tools: !CapsuleCommand
    run: vagga _capsule build tools
    description: install and setup tools needed for cross compilation
  dist: !Command
    container: tools
    run: |
      # set rustup to stable version
      rustup default stable

      # add target arch
      rustup target add x86_64-pc-windows-msvc 

      # run build script
      /work/vagga.sh
    description: package and distribute tauri packages
  sh: !Command
    container: tauri
    run: bash
    description: execute bash

vagga.sh

#!/bin/bash

BASEDIR="$(cd "$(dirname "$0")" && pwd)"

mkdir -p $BASEDIR/dist/windows
rm -rf $BASEDIR/dist/windows/*
cd $BASEDIR/src-tauri

# compile windows executable .exe release file
cargo build --release --target x86_64-pc-windows-msvc

# Copy files required to build .msi executable
cp -rvf $BASEDIR/License.rtf $BASEDIR/src-tauri/target/x86_64-pc-windows-msvc/release/
cp -rvf $BASEDIR/app.wxs $BASEDIR/src-tauri/target/x86_64-pc-windows-msvc/release/
cp -rvf $BASEDIR/src-tauri/icons/icon.ico $BASEDIR/src-tauri/target/x86_64-pc-windows-msvc/release/

# rename .exe file to name specified in app.wxs
mv $BASEDIR/src-tauri/target/x86_64-pc-windows-msvc/release/*.exe $BASEDIR/src-tauri/target/x86_64-pc-windows-msvc/release/tauriApp.exe

# build .msi executable and move to dist folder using wixl msitool
wixl -v $BASEDIR/src-tauri/target/x86_64-pc-windows-msvc/release/app.wxs -o $BASEDIR/dist/windows/tauriApp-x64.msi --ext ui -a x64

Final

Finally run vagga dist in the root application folder from terminal